package vs_a1;

import java.io.FileWriter;
import java.io.IOException;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.sql.Timestamp;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * RMI Server for the given MessageServerInterface.
 * 
 * @author Jan Quenzel
 * @author Rico Flaegel
 *
 */
public class ServerImplementation extends UnicastRemoteObject implements MessageServerInterface {
	
	private static final long serialVersionUID = 1L;
	/**
	 * the message Queue of our Server
	 */
	static DeliveryQueue queue = new DeliveryQueue(20);
	/**
	 * the hashMap containing the client memory
	 * contains ID's and received Messages
	 * => identity of a client
	 * needs to be synchronized!
	 */
	private Map<String,ClientType> hm;
	/**
	 * time to store the identity of a client
	 */
	private int memTime;
	/**
	 * Watchdog is watching for old entries in the client memory
	 */
	private WatchDog wauwi;
	/**
	 * the logger of the server
	 */
	private FileWriter lw;
	
	/**
	 * Constructor of our Server.
	 * @param t time in seconds, how long should a client be memorized. 
	 * @throws RemoteException thrown if it is not possible to create the UnicastRemoteObject
	 */
	public ServerImplementation(int t) throws RemoteException {
		super();
		hm = Collections.synchronizedMap(new HashMap<String,ClientType>()); // get synchronized HashMap
		setMemTime(t);
		wauwi = null;
	}
	
	/**
	 * Constructor of our Server.
	 * @throws RemoteException thrown if it is not possible to create the UnicastRemoteObject
	 */
	public ServerImplementation() throws RemoteException {
		this(5);
	}
	
	/**
	 * create a TimeStamp in SQL-Style, e.g. for logging
	 * @return the actual Timestamp
	 */
	public String createTimeStamp(){
		return (new Timestamp((new Date()).getTime())).toString();
	}
	
	/**
	 * Log a given string, save it to logfile.
	 * @param s what shall be logged.
	 */
	public synchronized void log(String s){
		try{
			lw.append(s+"\n");
			lw.flush();
		}catch(IOException e){
			e.printStackTrace();
		}
	}
	
	/**
	 * initialize Server ( start logger and start WatchDog)
	 * @throws IOException thrown if logger couldn't be started.
	 */
	public void initServer() throws IOException {
		lw = new FileWriter("log.txt");
		log( createTimeStamp() + " ServerImpl: Server started and log opened.");
		wauwi = new WatchDog(1000,this);
		Thread t = new Thread(wauwi);
		t.setDaemon(true);
		t.start();
		log( createTimeStamp() + " ServerImpl: Watchdog started.");
	}

	
	@Override
	public String getMessage(String clientID) throws RemoteException {
		Message m = null;
		ClientType c = null;
		// search for the clientID in memory or create one.
		synchronized(hm){
			if(hm.containsKey(clientID)){
				c = hm.get(clientID);
				m = queue.getMessageToClientID(clientID,c);
			}else{
				c = new ClientType(clientID);
				m = queue.getMessageToClientID(clientID,c);
			}
			if(m != null){
				c.add(m.getMessageID());
			}else{
				c.setActualTime();
			}
			if(c != null) hm.put(clientID, c);
		}
		if(m == null){ // no message found so send "no more messages" Exception
			log(createTimeStamp() +  " ServerImpl: No more Messages for " + clientID + ".");
			throw new RemoteException("no more messages");
		} else {
			log(createTimeStamp() +  " ServerImpl: " + clientID + " wanted Message " + m.getMessageID() + ".");
		}
		return m.deliverMessage();
	}

	@Override
	public void dropMessage(String clientID, String message) throws RemoteException {
		queue.add( new Message(clientID, message, new Date().getTime()));
		System.out.println(queue);
		log(createTimeStamp() +" ServerImpl: " + clientID + " dropped Message.");
	}

	/**
	 * retrieve the time in seconds of the memory
	 * @return number of seconds to forget a client. 
	 */
	public synchronized int getMemTime() {
		return memTime;
	}
	
	/**
	 * Set the time in seconds of the memory
	 * @param memTime number of seconds to forget a client. 
	 */
	public synchronized void setMemTime(int memTime) {
		this.memTime = memTime;
	}

	/**
	 * WatchDog controls the client memory to forget old entries.
	 */
	private class WatchDog implements Runnable{
		private boolean isRunning;
		private int sleepTime;
		private ServerImplementation par;

		/**
		 * Constructor
		 * @param stime time to wait between each search in millisconds.
		 * @param parent referenz to the ServerImplementation
		 */
		public WatchDog(int stime, ServerImplementation parent){
			this.sleepTime = stime;
			this.par = parent;
			isRunning = true;
		}
		/**
		 * Sets the Time spend sleeping between 2 searches through the Memory.
		 * @param t time in milliseconds
		 */
		@SuppressWarnings("unused")
		public synchronized void setSleepTime(int t){
			sleepTime = t;
		}
		/**
		 * Sets the Watchdog to running state.
		 * @param t the new value for isRunning
		 */
		@SuppressWarnings("unused")
		public synchronized void setRunning(boolean t){
			isRunning = t;
		}
		
		@Override
		public void run() {
			while(isRunning){
				synchronized(hm){
					for(String s : hm.keySet()){
						ClientType ct = hm.get(s);
						long timediff = par.getMemTime()*1000;
						long actTime = new Date().getTime();
						if( (ct.getTime().getTime() + timediff) < actTime){
							System.out.println("entry to " + s + " removed!");
							hm.remove(s);
							par.log(createTimeStamp() +" Watchdog:   " + s + " removed.");
						}
					}
				}
				try {
					TimeUnit.MILLISECONDS.sleep(sleepTime);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
}
